\chapter{面向对象}

\section{封装}

\subsection{类与对象}

在面向对象编程中，把构成问题的事物分解成各个对象，每个对象都有自己的数据和行为，程序通过对象之间的交互来实现功能。\\

类（class）是一个模板，定义了对象的属性和方法，用来描述同一类对象的共同特征和行为。对象（object）是类的实例，它具有类定义的属性和方法。\\

关键字new实例化并返回一个指向类对象的指针，之后就可以通过访问对象的属性和方法来操作对象。使用new分配的对象，需要使用delete释放。\\

\mybox{银行账户}

\begin{lstlisting}[language=C++]
#include <iostream>
#include <string>

using namespace std;

class BankAccount {
public:
    string owner;
    string account;
    double balance;

    void deposit(double amount) {
        balance += amount;
    }

    void withdraw(double amount) {
        balance -= amount;
    }
};

int main() {
    BankAccount account;
    account.owner = "Terry";
    account.account = "6250941006528599";
    account.balance = 50;

    cout << "Owner: " << account.owner << endl;
    cout << "Account: " << account.account << endl;
    cout << "Balance: " << account.balance << endl;

    account.deposit(100);
    cout << "Balance: " << account.balance << endl;

    account.withdraw(70);
    cout << "Balance: " << account.balance << endl;

    return 0;
}
\end{lstlisting}

\begin{tcolorbox}
    \mybox{运行结果}
    \begin{verbatim}
Owner: Terry
Account: 6250941006528599
Balance: 50
Balance: 150
Balance: 80
\end{verbatim}
\end{tcolorbox}

\vspace{0.5cm}

\subsection{封装（Encapsulation）}

封装是面向对象的重要原则，尽可能隐藏对象的内部实现细节。封装可以认为是一个保护屏障，防止该类的数据被外部随意访问。当要访问该类的数据时，必须通过指定的接口。合适的封装可以让代码更容易理解和维护，也加强了程序的安全性。\\

为了实现封装，需要对类的属性和方法进行访问权限的控制：

\begin{enumerate}
    \item public：允许任何地方访问。
    \item private：只允许在类的内部访问。
    \item protected：只允许在类的内部和子类中访问。
\end{enumerate}

通常会将类的属性设置为private，然后对外提供一对setter/getter方法来访问该属性。\\

为了避免方法的参数与类的属性重名造成歧义，可以使用this关键字用来指代当前对象。\\

\mybox{银行账户}

\begin{lstlisting}[language=C++]
#include <iostream>
#include <string>

using namespace std;

class BankAccount {
    private:
    const size_t ACCOUNT_DIGITS = 16;
    string owner;
    string account;
    double balance;

    public:
    void setOwner(string owner) {
        if (!owner.empty()) {
            this->owner = owner;
        }
    }

    string getOwner() {
        return owner;
    }

    void setAccount(string account) {
        if (account.length() == ACCOUNT_DIGITS) {
            this->account = account;
        }
    }

    string getAccount() {
        return account;
    }

    void setBalance(double balance) {
        if (balance >= 0) {
            this->balance = balance;
        }
    }

    double getBalance() {
        return balance;
    }

    bool deposit(double amount) {
        if (amount <= 0) {
            return false;
        }
        balance += amount;
        return true;
    }

    bool withdraw(double amount) {
        if (amount <= 0 || amount > balance) {
            return false;
        }
        balance -= amount;
        return true;
    }
};

int main() {
    BankAccount account;
    account.setOwner("Terry");
    account.setAccount("6250941006528599");
    account.setBalance(50);

    cout << "Owner: " << account.getOwner() << endl;
    cout << "Account: " << account.getAccount() << endl;
    cout << "Balance: " << account.getBalance() << endl;

    account.deposit(100);
    cout << "Balance: " << account.getBalance() << endl;

    account.withdraw(70);
    cout << "Balance: " << account.getBalance() << endl;

    return 0;
}
\end{lstlisting}

\begin{tcolorbox}
    \mybox{运行结果}
    \begin{verbatim}
Owner: Terry
Account: 6250941006528599
Balance: 50
Balance: 150
Balance: 80
\end{verbatim}
\end{tcolorbox}

\newpage

\section{构造函数}

\subsection{构造方法（Constructor）}

构造方法是一种特殊的方法，会在创建对象时自动调用，用于创建并初始化对象。每个类可以有一个或多个构造方法，构造方法的名字必须和类名一致。构造方法没有返回值，返回值类型部分不写。

\vspace{-0.5cm}

\begin{lstlisting}[language=C++]
BankAccount() {
    owner = "admin";
    account = "0000000000000000";
    balance = 0;
}
\end{lstlisting}

如果一个类中没有写构造方法，系统会自动提供一个public的无参构造方法，以便实例化对象。如果一个类中已经写了构造方法，系统将不会再提供默认的无参构造方法。

\vspace{-0.5cm}

\begin{lstlisting}[language=C++]
BankAccount(string owner, string account, double balance) {
    if (!owner.empty()) {
        this->owner = owner;
    }

    if (account.length() == ACCOUNT_DIGITS) {
        this->account = account;
    }

    if (balance >= 0) {
        this->balance = balance;
    }
}
\end{lstlisting}

析构函数（Destructor）是一种特殊的方法，会在对象被销毁时自动调用，用于释放对象占用的资源。析构函数的名字必须和类名一致，前面加上~，没有返回值，返回值类型部分不写。

\vspace{-0.5cm}

\begin{lstlisting}[language=C++]
~BankAccount() {
    // code
}
\end{lstlisting}

\vspace{0.5cm}

\subsection{重载（Overload）}

重载用于在同一个类定义多个同名方法，但是这些方法的参数列表不同。重载的主要用途是提供方法的多种版本，以便满足不同的需求。\\

重载还可以使代码更具可读性，因为它使得方法名更具描述性，而不必考虑特定的参数列表。\\

\mybox{银行账户}

\begin{lstlisting}[language=C++]
#include <iostream>
#include <string>

using namespace std;

class BankAccount {
    private:
    const size_t ACCOUNT_DIGITS = 16;
    string owner;
    string account;
    double balance;

    public:
    BankAccount() {
        owner = "admin";
        account = "0000000000000000";
        balance = 0;
    }

    BankAccount(string owner, string account, double balance) {
        if (!owner.empty()) {
            this->owner = owner;
        }

        if (account.length() == ACCOUNT_DIGITS) {
            this->account = account;
        }

        if (balance >= 0) {
            this->balance = balance;
        }
    }

    void setOwner(string owner) {
        if (!owner.empty()) {
            this->owner = owner;
        }
    }

    string getOwner() {
        return owner;
    }

    void setAccount(string account) {
        if (account.length() == ACCOUNT_DIGITS) {
            this->account = account;
        }
    }

    string getAccount() {
        return account;
    }

    void setBalance(double balance) {
        if (balance >= 0) {
            this->balance = balance;
        }
    }

    double getBalance() {
        return balance;
    }

    bool deposit(double amount) {
        if (amount <= 0) {
            return false;
        }
        balance += amount;
        return true;
    }

    bool withdraw(double amount) {
        if (amount <= 0 || amount > balance) {
            return false;
        }
        balance -= amount;
        return true;
    }

    bool withdraw(double amount, double fee) {
        if (amount <= 0 || amount + fee > balance) {
            return false;
        }

        balance -= amount + fee;
        return true;
    }
};

int main() {
    BankAccount account1;
    cout << "Account 1 Owner: " << account1.getOwner() << endl;
    cout << "Account 1 Account: " << account1.getAccount() << endl;
    cout << "Account 1 Balance: " << account1.getBalance() << endl;

    BankAccount account2("Terry", "6250941006528599", 50);
    cout << "Account 2 Balance: " << account2.getBalance() << endl;

    account2.withdraw(20);
    cout << "Account 2 Balance: " << account2.getBalance() << endl;

    account2.withdraw(10, 1);
    cout << "Account 2 Balance: " << account2.getBalance() << endl;

    return 0;
}
\end{lstlisting}

\begin{tcolorbox}
    \mybox{运行结果}
    \begin{verbatim}
Account 1 Owner: admin
Account 1 Account: 0000000000000000
Account 1 Balance: 0
Account 2 Balance: 50
Account 2 Balance: 30
Account 2 Balance: 19
	\end{verbatim}
\end{tcolorbox}

\newpage

\section{友元}

\subsection{友元函数}

封装使得类的数据对外隐藏，但是有些函数不是类的一部分，却又需要频繁访问类的数据成员，这时可以将这些函数定义为该类的友元函数。一个函数可以是多个类的友元函数，只需要在各个类中分别声明。\\

友元函数是可以直接访问类的私有成员和非成员函数。它是定义在类外的普通函数，它不属于任何类，但需要在类的定义声明。\\

友元的作用是提高程序的运行效率，但它破坏了类的封装性，使得非成员函数可以访问类的私有成员。\\

\mybox{友元函数}

\begin{lstlisting}[language=C++]
#include <iostream>
#include <cmath>

using namespace std;

class Coordinate {
    private:
    double x;
    double y;

    public:
    Coordinate(double x, double y) : x(x), y(y) {}

    friend double distance(Coordinate &c1, Coordinate &c2);
};

double distance(Coordinate &c1, Coordinate &c2) {
    double delta_x = c1.x - c2.x;
    double delta_y = c1.y - c2.y;
    return sqrt(delta_x * delta_x + delta_y * delta_y);
}

int main() {
    Coordinate c1(0, 0);
    Coordinate c2(3, 4);
    cout << distance(c1, c2) << endl;
    return 0;
}
\end{lstlisting}

\begin{tcolorbox}
    \mybox{运行结果}
    \begin{verbatim}
5
	\end{verbatim}
\end{tcolorbox}

\newpage

\section{运算符重载}

\subsection{二元运算符重载}

运算符重载是指在类中重新定义运算符的行为，使得运算符能够作用于类的对象上。\\

运算符重载通过定义运算符函数实现，函数名由关键字operator和要重载的运算符组成。\\

\mybox{复数}

\begin{lstlisting}[language=C++]
#include <iostream>
#include <string>

using namespace std;

class Complex {
    private:
    double real;
    double imag;

    public:
    Complex(double real=0, double imag=0) : real(real), imag(imag) {}

    string getNumber() {
        return to_string(real) + "+" + to_string(imag) + "i";
    }

    Complex operator+(const Complex& c) {
        Complex complex;
        complex.real = this->real + c.real;
        complex.imag = this->imag + c.imag;
        return complex;
    }
};

int main() {
    Complex c1(1, 2);
    Complex c2(3, 4);
    Complex result = c1 + c2;
    cout << result.getNumber() << endl;
    return 0;
}
\end{lstlisting}

\begin{tcolorbox}
    \mybox{运行结果}
    \begin{verbatim}
4.000000+6.000000i
	\end{verbatim}
\end{tcolorbox}

\vspace{0.5cm}

\subsection{输入输出运算符重载}

通过重载输入输出流运算符可以实现对自定义类的输入输出。\\

\mybox{复数}

\begin{lstlisting}[language=C++]
#include <iostream>
#include <string>

using namespace std;

class Complex {
    private:
    double real;
    double imag;

    public:
    Complex(double real=0, double imag=0) : real(real), imag(imag) {}

    Complex operator+(const Complex& c) {
        Complex complex;
        complex.real = this->real + c.real;
        complex.imag = this->imag + c.imag;
        return complex;
    }

    friend ostream& operator<<(ostream& os, const Complex& c);
};

ostream& operator<<(ostream& os, const Complex& c) {
    os << c.real << " + " << c.imag << "i";
    return os;
}

int main() {
    Complex c1(1, 2);
    Complex c2(3, 4);
    Complex result = c1 + c2;
    cout << result << endl;
    return 0;
}
\end{lstlisting}

\begin{tcolorbox}
    \mybox{运行结果}
    \begin{verbatim}
4 + 6i
	\end{verbatim}
\end{tcolorbox}

\newpage

\section{继承}

\subsection{继承（Inheritance）}

继承指一个类可以继承另一个类的特征和行为，并可以对其进行扩展。这样就可以避免在多个类中重复定义相同的特征和行为。\\

\begin{figure}[H]
    \centering
    \begin{tikzpicture}
        \begin{class}[text width = 5cm]{Product}{0,0}
            \attribute{- name : string}
            \attribute{- price : double}
            \operation{+ Product(string, double)}
            \operation{+ get\_name() : string}
            \operation{+ get\_price() : double}
            \operation{+ set\_name(string) : void}
            \operation{+ set\_price(double) : void}
        \end{class}

        \begin{class}[text width = 6cm]{Food}{-4,-7}
            \inherit{Product}
            \attribute{- calories : int}
            \operation{+ Food(string, double, int)}
            \operation{+ get\_calories() : int}
            \operation{+ set\_calories(int) : void}
        \end{class}

        \begin{class}[text width = 6cm]{Drink}{4,-7}
            \inherit{Product}
            \attribute{- size : String}
            \operation{+ Drink(string, double, string)}
            \operation{+ get\_size() : string}
            \operation{+ set\_size(string) : void}
        \end{class}
    \end{tikzpicture}
    \caption{继承}
\end{figure}

产生继承关系后，子类可以通过调用父类中的属性和方法，也可以定义子类独有的属性和方法。\\

在创建子类对象时，会先调用父类的构造方法，然后再调用子类的构造方法。因此父类中必须存在一个构造方法，否则将无法创建子类对象。\\

\mybox{麦当劳}

\begin{lstlisting}[language=C++]
#ifndef _PRODUCT_H_
#define _PRODUCT_H_

#include <string>

class Product {
    protected:
    std::string name;
    double price;

    public:
    Product(std::string name, double price);
    std::string get_name();
    void set_name(std::string name);
    double get_price();
    void set_price(double price);
};

#endif
\end{lstlisting}

\begin{lstlisting}[language=C++]
#include "product.h"

Product::Product(std::string name, double price) {
    this->name = name;
    this->price = price;
}

std::string Product::get_name() {
    return name;
}

void Product::set_name(std::string name) {
    this->name = name;
}

double Product::get_price() {
    return price;
}

void Product::set_price(double price) {
    this->price = price;
}
\end{lstlisting}

\begin{lstlisting}[language=C++]
#ifndef _FOOG_H_
#define _FOOD_H_

#include <iostream>
#include <string>
#include "product.h"

class Food : public Product {
    private:
    int calories;

    public:
    Food(std::string name, double price, int calories);
    int get_calories();
    void set_calories(int calories);
    friend std::ostream& operator<<(std::ostream& os,
                                    const Food& food);
};

#endif
\end{lstlisting}

\begin{lstlisting}[language=C++]
#include "food.h"

Food::Food(std::string name, double price, int calories)
    : Product(name, price) {
    this->calories = calories;
}

int Food::get_calories() {
    return calories;
}

void Food::set_calories(int calories) {
    this->calories = calories;
}

std::ostream& operator<<(std::ostream& os, const Food& food) {
    os << "Food: " << food.name
       << " ($" << food.price << ") "
       << food.calories;
    return os;
}
\end{lstlisting}

\begin{lstlisting}[language=C++]
#ifndef _DRINK_H_
#define _DRINK_H_

#include <iostream>
#include <string>
#include "product.h"

class Drink : public Product {
    private:
    std::string size;

    public:
    Drink(std::string name, double price, std::string size);
    std::string get_size();
    void set_size(std::string size);
    friend std::ostream& operator<<(std::ostream& os,
                                    const Drink& drink);
};

#endif
\end{lstlisting}

\begin{lstlisting}[language=C++]
#include "drink.h"

Drink::Drink(std::string name, double price, std::string size)
    : Product(name, price) {
    this->size = size;
}

std::string Drink::get_size() {
    return size;
}

void Drink::set_size(std::string size) {
    this->size = size;
}

std::ostream& operator<<(std::ostream& os, const Drink& drink) {
    os << "Drink: " << drink.name
       << " ($" << drink.price << ") " 
       << drink.size;
    return os;
}
\end{lstlisting}

\begin{lstlisting}[language=C++]
#include <iostream>
#include "food.h"
#include "drink.h"

using namespace std;

int main() {
    Food food("Cheeseburger", 5.45, 302);
    Drink drink("Coke", 3.7, "Large");

    cout << food << endl;
    cout << drink << endl;

    return 0;
}
\end{lstlisting}

\begin{tcolorbox}
    \mybox{运行结果}
    \begin{verbatim}
Food: Cheeseburger ($5.45) 302
Drink: Coke ($3.7) Large
	\end{verbatim}
\end{tcolorbox}

\newpage

\section{抽象类}

\subsection{虚函数}

有些类只能用来做继承，不能用于创建对象。例如在动物园中并不存在“动物”这个对象，只有动物的子类对象，因此动物类不应该被实例化。\\

抽象类是一种不能被实例化的类，它用于定义接口或公共实现，供其它类继承并实现。\\

有时候父类提供的方法无法满足子类不同的需求，但是如果不定义该方法，就表示该类具有该行为。\\

这种情况就可以将这个父类的方法定义为虚函数，这样所有的子类都必须要重写该方法，否则子类仍然为抽象类。\\

\mybox{动物}

\begin{lstlisting}[language=C++]
#ifndef _ANIMAL_H_
#define _ANIMAL_H_

#include <string>

class Animal {
    public:
    virtual std::string sound() = 0;
};

#endif
\end{lstlisting}

\begin{lstlisting}[language=C++]
#ifndef _DOG_H_
#define _DOG_H_

#include "animal.h"

class Dog : public Animal {
    public:
    virtual std::string sound() override;
};

#endif
\end{lstlisting}

\begin{lstlisting}[language=C++]
#include "dog.h"
#include <iostream>

std::string Dog::sound() {
    return "Woof";
}
\end{lstlisting}

\begin{lstlisting}[language=C++]
#ifndef _CAT_H_
#define _CAT_H_

#include "animal.h"

class Cat : public Animal {
    public:
    virtual std::string sound() override;
};

#endif
\end{lstlisting}

\begin{lstlisting}[language=C++]
#include "cat.h"
#include <iostream>

std::string Cat::sound() {
    return "Meow";
}
\end{lstlisting}

\begin{lstlisting}[language=C++]
#include <iostream>
#include "dog.h"
#include "cat.h"

using namespace std;

int main() {
    Dog dog;
    Cat cat;

    cout << "Dog's sound: " << dog.sound() << endl;
    cout << "Cat's sound: " << cat.sound() << endl;

    return 0;
}
\end{lstlisting}

\begin{tcolorbox}
    \mybox{运行结果}
    \begin{verbatim}
Dog's sound: Woof
Cat's sound: Meow
	\end{verbatim}
\end{tcolorbox}

\newpage

\section{多态}

\subsection{多态（Polymorphism）}

多态是指对象可以具有多种形态，即同一个对象在不同时刻表现出不同的行为。例如Dog和Cat都是Animal的子类，因此可以将子类对象赋值给父类引用，从而产生多种形态。

\vspace{-0.5cm}

\begin{lstlisting}[language=Java]
Animal animal = new Dog();
\end{lstlisting}

由子类类型转型为父类类型，称为向上转型。由父类类型转型为子类类型，称为向下转型。\\

\mybox{员工工资}

\begin{lstlisting}[language=C++]
#ifndef _EMPLOYEE_H_
#define _EMPLOYEE_H_

#include <string>

class Employee {
    protected:
    std::string name;

    public:
    Employee(std::string name);
    std::string get_name();
    virtual double get_salary() = 0;
};

#endif
\end{lstlisting}

\begin{lstlisting}[language=C++]
#include "employee.h"

Employee::Employee(std::string name) {
    this->name = name;
}

std::string Employee::get_name() {
    return name;
}
\end{lstlisting}

\begin{lstlisting}[language=C++]
#ifndef _FULL_TIME_EMPLOYEE_H_
#define _FULL_TIME_EMPLOYEE_H_

#include "employee.h"

class FullTimeEmployee : public Employee {
    private:
    double basic_salary;
    double bonus;

    public:
    FullTimeEmployee(std::string name,
                     double basic_salary,
                     double bonus);
    virtual double get_salary() override;
};

#endif
\end{lstlisting}

\begin{lstlisting}[language=C++]
#include "full_time_employee.h"

FullTimeEmployee::FullTimeEmployee(std::string name,
                                    double basic_salary,
                                    double bonus)
        : Employee(name) {
    this->basic_salary = basic_salary;
    this->bonus = bonus;
}

double FullTimeEmployee::get_salary() {
    return basic_salary + bonus;
}
\end{lstlisting}

\begin{lstlisting}[language=C++]
#ifndef _PART_TIME_EMPLOYEE_H_
#define _PART_TIME_EMPLOYEE_H_

#include "employee.h"

class PartTimeEmployee : public Employee {
    private:
    double daily_wage;
    int working_days;

    public:
    PartTimeEmployee(std::string name,
                     double daily_wage,
                     int working_days);
    virtual double get_salary() override;
};

#endif
\end{lstlisting}

\begin{lstlisting}[language=C++]
#include "part_time_employee.h"

PartTimeEmployee::PartTimeEmployee(std::string name,
                                    double daily_wage,
                                    int working_days)
        : Employee(name) {
    this->daily_wage = daily_wage;
    this->working_days = working_days;
}

double PartTimeEmployee::get_salary() {
    return daily_wage * working_days;
}
\end{lstlisting}

\begin{lstlisting}[language=C++]
#include <iostream>
#include "full_time_employee.h"
#include "part_time_employee.h"

using namespace std;

int main() {
    Employee *employees[2] = {
        new FullTimeEmployee("Alice", 5783, 173),
        new PartTimeEmployee("Bob", 150, 15),
    };

    for (Employee *employee : employees) {
        cout << employee->get_name() << ": $"
             << employee->get_salary() << endl;
    }

    return 0;
}
\end{lstlisting}

\begin{tcolorbox}
    \mybox{运行结果}
    \begin{verbatim}
Alice: $5956
Bob: $2250
	\end{verbatim}
\end{tcolorbox}

\newpage